import osfrom pandas.plotting import autocorrelation_plotimport warningsimport pandas as pdimport seaborn as snsimport requestsimport plotly.express as pximport matplotlib.pyplot as pltimport matplotlib.image as mpimgimport plotly.graph_objects as gofrom IPython.display import displayfrom scipy.stats import ks_2samp# from TSPackages import *from scipy.stats import jarque_berafrom scipy.stats import ks_2sampfrom scipy.stats import kurtosis, skewfrom statsmodels.graphics.tsaplots import plot_acf, plot_pacfwarnings.filterwarnings('ignore')from pandas.plotting import autocorrelation_plotimport pandas as pdimport matplotlib.pyplot as pltfrom statsmodels.tsa.seasonal import seasonal_decomposeimport matplotlib.pyplot as pltimport os
Code
file_path = [r"C:\Users\Keyla Alba\OneDrive - Universidad del Norte\Doctorado (Ciencias)\Move La America\EDA\Datos\INMET_CO_MS_A710_PARANAIBA_01-01-2019_A_31-12-2019.CSV",r"C:\Users\Keyla Alba\OneDrive - Universidad del Norte\Doctorado (Ciencias)\Move La America\EDA\Datos\INMET_CO_MS_A710_PARANAIBA_01-01-2020_A_31-12-2020.CSV",r"C:\Users\Keyla Alba\OneDrive - Universidad del Norte\Doctorado (Ciencias)\Move La America\EDA\Datos\INMET_CO_MS_A710_PARANAIBA_01-01-2021_A_31-12-2021.CSV",r"C:\Users\Keyla Alba\OneDrive - Universidad del Norte\Doctorado (Ciencias)\Move La America\EDA\Datos\INMET_CO_MS_A710_PARANAIBA_01-01-2022_A_31-12-2022.CSV",r"C:\Users\Keyla Alba\OneDrive - Universidad del Norte\Doctorado (Ciencias)\Move La America\EDA\Datos\INMET_CO_MS_A710_PARANAIBA_01-01-2023_A_31-12-2023.CSV",r"C:\Users\Keyla Alba\OneDrive - Universidad del Norte\Doctorado (Ciencias)\Move La America\EDA\Datos\INMET_CO_MS_A710_PARANAIBA_01-01-2024_A_31-12-2024.CSV",r"C:\Users\Keyla Alba\OneDrive - Universidad del Norte\Doctorado (Ciencias)\Move La America\EDA\Datos\INMET_CO_MS_A710_PARANAIBA_01-01-2025_A_28-02-2025.CSV"]def load_and_clean(file_path): df = pd.read_csv(file_path, sep=';', encoding='latin1', skiprows=8) df.columns = [col.strip().upper() for col in df.columns]for col in df.columns:if"RADIACAO"in col and"KJ"in col: df.rename(columns={col: "RADIACAO_GLOBAL"}, inplace=True)break df['HORA UTC'] = df['HORA UTC'].astype(str).str.replace(' UTC', '', regex=False) df['HORA UTC'] = df['HORA UTC'].str.zfill(4) df['HORA UTC'] = df['HORA UTC'].str[:2] +':'+ df['HORA UTC'].str[2:4] df['datetime'] = pd.to_datetime(df['DATA'] +' '+ df['HORA UTC'], format='%Y/%m/%d %H:%M', errors='coerce') df['RADIACAO_GLOBAL'] = pd.to_numeric(df['RADIACAO_GLOBAL'], errors='coerce').fillna(0)return df[['datetime', 'RADIACAO_GLOBAL']]df_INMET = pd.concat([load_and_clean(fp) for fp in file_path], ignore_index=True)print(df_INMET.shape)df_INMET.head(n=14)
(54024, 2)
datetime
RADIACAO_GLOBAL
0
2019-01-01 00:00:00
0.0
1
2019-01-01 01:00:00
0.0
2
2019-01-01 02:00:00
0.0
3
2019-01-01 03:00:00
0.0
4
2019-01-01 04:00:00
0.0
5
2019-01-01 05:00:00
0.0
6
2019-01-01 06:00:00
0.0
7
2019-01-01 07:00:00
0.0
8
2019-01-01 08:00:00
0.0
9
2019-01-01 09:00:00
0.0
10
2019-01-01 10:00:00
0.0
11
2019-01-01 11:00:00
0.0
12
2019-01-01 12:00:00
0.0
13
2019-01-01 13:00:00
2434.0
Code
fig_INMET = px.line( df_INMET, x='datetime', y='RADIACAO_GLOBAL', title='Radiación Global (KJ/m²) por Hora INMET (2019 a Febrero 2025)', labels={'datetime': 'Fecha y Hora','RADIACAO_GLOBAL': 'Radiación (KJ/m²)' })fig_INMET.update_layout(template='plotly_white', width=1100, height=500)fig_INMET.show()
3.0.0.1Imputación usando promedio estacional
Uno de los retos fundamentales en el análisis de series temporales de radiación solar es la presencia de valores faltantes, especialmente en estaciones meteorológicas con limitaciones técnicas o períodos de inestabilidad climática. Una alternativa eficaz para abordar este problema es la Regla de Imputación Estacional, la cual consiste en reemplazar los datos perdidos utilizando el promedio de los valores registrados en la misma hora y día de otros años o meses, respetando así la estacionalidad y patrones cíclicos propios de la variable. Esta técnica resulta particularmente adecuada para variables como la radiación solar, que presenta una fuerte dependencia temporal y comportamiento periódico. En este contexto, analizaron el rendimiento de métodos univariantes de imputación bajo diferentes condiciones climáticas, concluyendo que las estrategias basadas en estacionalidad ofrecen ventajas significativas en climas tropicales. De manera complementaria, enfatizan que una adecuada imputación mejora sustancialmente la precisión de modelos de predicción basados en aprendizaje automático, al reducir la incertidumbre asociada al preprocesamiento de datos meteorológicos.
Regla de Imputación Estacional
Supongamos que tienes una serie de datos:
\(R_i\): valor de radiación en la observación \(i\)
\(d_i\): día del año de la observación \(i\) (de 1 a 366)
\(h_i\): hora del día de la observación \(i\) (de 0 a 23)
Para cada observación con valor faltante \(R_i = 0\), la imputación es:
rad_orig = df_INMET['RADIACAO_GLOBAL'][df_INMET['RADIACAO_GLOBAL'] >0]rad_imp = df_INMET['RADIACAO_GLOBAL_IMPUTADA'][df_INMET['RADIACAO_GLOBAL_IMPUTADA'] >0]fig, axes = plt.subplots(1, 2, figsize=(16, 5), sharey=True)sns.histplot(rad_orig, bins=60, kde=True, color='royalblue', ax=axes[0])axes[0].set_title('RADIACAO_GLOBAL (> 0)')axes[0].set_xlabel('Radiación (KJ/m²)')axes[0].set_ylabel('Frecuencia')axes[0].grid(True)sns.histplot(rad_imp, bins=60, kde=True, color='darkorange', ax=axes[1])axes[1].set_title('RADIACAO_GLOBAL_IMPUTADA (> 0)')axes[1].set_xlabel('Radiación (KJ/m²)')axes[1].set_ylabel('')axes[1].grid(True)fig.suptitle('Distribuciones de Radiación Solar INMET (Imputación Promedio Estacional) (> 0) - Antes y Después ', fontsize=16)plt.tight_layout()plt.show()rad_orig = df_INMET['RADIACAO_GLOBAL'][df_INMET['RADIACAO_GLOBAL'] >0]rad_imp = df_INMET['RADIACAO_GLOBAL_IMPUTADA'][df_INMET['RADIACAO_GLOBAL_IMPUTADA'] >0]stat_ks, p_ks = ks_2samp(rad_orig, rad_imp)print("Kolmogorov-Smirnov Test (Distribución Original vs Imputada):")print(f"Estadístico KS: {stat_ks:.4f}")print(f"p-valor: {p_ks:.4e}")if p_ks >0.05:print("No se rechaza H0 → Las distribuciones son estadísticamente similares.")else:print("Se rechaza H0 → Las distribuciones son diferentes.")
Kolmogorov-Smirnov Test (Distribución Original vs Imputada):
Estadístico KS: 0.0153
p-valor: 8.3915e-01
No se rechaza H0 → Las distribuciones son estadísticamente similares.
3.0.0.2EDA
En el contexto de la predicción de radiación solar mediante modelos de series de tiempo, es fundamental conservar la estructura completa de la serie, incluyendo los valores de radiación nocturna igual a cero. El mantenimiento de esta continuidad temporal asegura que los algoritmos puedan captar correctamente la dinámica estacional y los ciclos diarios, evitando la introducción de sesgos por omisión de periodos sin registros. Este enfoque es especialmente relevante al trabajar con modelos como redes neuronales recurrentes, regresión de soporte vectorial o procesos gaussianos, los cuales requieren secuencias homogéneas y completas para una convergencia adecuada y generalización efectiva .
Adicionalmente, investigaciones que comparan arquitecturas híbridas de redes neuronales han resaltado que la inclusión de todos los datos horarios incluyendo los correspondientes a la noche contribuye a un entrenamiento más robusto y coherente, permitiendo una mejor adaptación a diferentes contextos geográficos y meteorológicos .
variables = ['RADIACAO_GLOBAL_IMPUTADA']resumen = {}for var in variables: data = df_INMET[var] resumen[var] = {'N_records': len(data),'μ': data.mean(),'σ': data.std(),'y_min': data.min(),'Q1': data.quantile(0.25),'x̄': data.median(),'Q3': data.quantile(0.75),'y_max': data.max(),'Kurtosis': data.kurtosis(),'Skewness': data.skew() }df_resumen = pd.DataFrame(resumen)df_resumen
RADIACAO_GLOBAL_IMPUTADA
N_records
54024.000000
μ
284.327790
σ
752.818778
y_min
0.000000
Q1
0.000000
x̄
0.000000
Q3
0.000000
y_max
3924.000000
Kurtosis
6.500850
Skewness
2.742065
Code
from statsmodels.tsa.seasonal import seasonal_decomposedescomposicion = seasonal_decompose(df_INMET['RADIACAO_GLOBAL_IMPUTADA'].tail(1000), model='additive', period=24)descomposicion.plot()plt.suptitle("Decomposition of Global Radiation (Daily Cycle)", fontsize=16)plt.tight_layout()plt.show()
descomposicion = seasonal_decompose(df_INMET['RADIACAO_GLOBAL_IMPUTADA'].tail(1000), model='additive', period=24)fig = descomposicion.plot()fig.suptitle("Decomposition of Global Radiation (Daily Cycle)", fontsize=16)plt.tight_layout()fig.savefig('figures/decomposition_global_radiation.png')plt.show()
Code
os.makedirs('figures', exist_ok=True)media_por_hora = df_INMET.groupby('hour')['RADIACAO_GLOBAL_IMPUTADA'].mean().reset_index()plt.figure(figsize=(8, 6))sns.lineplot(data=media_por_hora, x='hour', y='RADIACAO_GLOBAL_IMPUTADA', marker='o')plt.title('Average Global Radiation by Hour', fontsize=16)plt.xlabel('Hour of Day', fontsize=12)plt.ylabel('Global Radiation', fontsize=12)plt.xticks(range(0, 24))plt.grid(True)plt.savefig('figures/avg_radiation_by_hour.png')plt.show()
Code
os.makedirs('figures', exist_ok=True)if'hour'notin df_INMET.columns: df_INMET['hour'] = df_INMET.index.hourfig = px.box( df_INMET, x='hour', y='RADIACAO_GLOBAL_IMPUTADA', labels={'hour': 'Hour of Day','RADIACAO_GLOBAL_IMPUTADA': 'Global Radiation' }, title='Distribution of Global Radiation by Hour of the Day')fig.update_layout( title_font_size=18, xaxis_title_font_size=14, yaxis_title_font_size=14)fig.write_html("figures/boxplot_radiation_by_hour_plotly.html")fig.show()
Code
os.makedirs('figures', exist_ok=True)if'hour'notin df_INMET.columns: df_INMET['hour'] = df_INMET.index.hourplt.figure(figsize=(10, 6))sns.boxplot(data=df_INMET, x='hour', y='RADIACAO_GLOBAL_IMPUTADA')plt.title('Distribution of Global Radiation by Hour of the Day', fontsize=18)plt.xlabel('Hour of Day', fontsize=14)plt.ylabel('Global Radiation', fontsize=14)plt.grid(True)plt.tight_layout()plt.savefig('figures/boxplot_radiation_by_hour.png', dpi=300)plt.show()
Code
os.makedirs('figures', exist_ok=True)plt.figure(figsize=(5, 4))autocorrelation_plot(df_INMET['RADIACAO_GLOBAL_IMPUTADA'].tail(200))plt.title("Autocorrelation - Global Radiation", fontsize=14)plt.grid(True)plt.savefig("figures/autocorrelation_global_radiation.png")plt.show()
Code
from statsmodels.graphics.tsaplots import plot_pacfos.makedirs("figures", exist_ok=True)plt.figure(figsize=(5, 4))plot_pacf(df_INMET['RADIACAO_GLOBAL_IMPUTADA'].tail(2000), lags=100, method='ywm')plt.title("PACF - Global Radiation (Hourly Series)", fontsize=12)plt.grid(True)plt.tight_layout()plt.savefig("figures/pacf_hourly_compact.png")plt.show()
<Figure size 500x400 with 0 Axes>
3.0.0.3EDA DIARIA
Code
df_diario = df_INMET['RADIACAO_GLOBAL_IMPUTADA'].resample('D').mean()variables = ['RADIACAO_GLOBAL_IMPUTADA']resumen = {}for var in variables: data = df_diario resumen[var] = {'N_records': len(data),'μ': data.mean(),'σ': data.std(),'y_min': data.min(),'Q1': data.quantile(0.25),'x̄': data.median(),'Q3': data.quantile(0.75),'y_max': data.max(),'Kurtosis': data.kurtosis(),'Skewness': data.skew() }df_resumen = pd.DataFrame(resumen)df_resumen
RADIACAO_GLOBAL_IMPUTADA
N_records
2251.000000
μ
284.327790
σ
142.219504
y_min
0.000000
Q1
187.916667
x̄
263.104167
Q3
369.541667
y_max
799.916667
Kurtosis
0.287621
Skewness
0.546791
Code
os.makedirs("figures", exist_ok=True)interval_start ="2022-01-01"interval_end ="2022-03-01"fig, ax = plt.subplots(figsize=(14, 5))ax.plot(df_diario, color='blue')ax.set_title('Daily Time Series - Global Radiation', fontsize=18)ax.set_xlabel('Date', fontsize=12)ax.set_ylabel('Global Radiation', fontsize=12)ax.grid(True)ax.axvspan(pd.to_datetime(interval_start), pd.to_datetime(interval_end), color='gray', alpha=0.3, label='Zoom region')ax_inset = fig.add_axes([0.55, 0.50, 0.3, 0.35]) # [x, y, width, height]zoom_data = df_diario.loc[interval_start:interval_end]ax_inset.plot(zoom_data, color='red')ax_inset.set_title('Zoom: Jan–Mar 2022', fontsize=10)ax_inset.set_xticks([])ax_inset.set_yticks([])plt.tight_layout()plt.savefig("figures/daily_series_with_zoom_highlighted.png")plt.show()
Code
descomposicion = seasonal_decompose(df_diario.tail(200), model='additive', period=7)descomposicion.plot()plt.suptitle('Descomposición de Radiación Global (ciclo semanal - 7 días)', fontsize=16)plt.tight_layout()plt.show()
os.makedirs("figures", exist_ok=True)average_by_day = df_INMET.groupby(df_INMET.index.day_name())['RADIACAO_GLOBAL_IMPUTADA'].mean().reindex( ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']).reset_index()average_by_day.columns = ['Day', 'Average Radiation']plt.figure(figsize=(8, 6))sns.lineplot(data=average_by_day, x='Day', y='Average Radiation', marker='o')plt.title('Average Global Radiation by Day of the Week', fontsize=18)plt.xlabel('Day of the Week', fontsize=14)plt.ylabel('Global Radiation', fontsize=14)plt.grid(True)plt.tight_layout()plt.savefig("figures/Average_Radiation_by_Day.png", dpi=300)plt.show()
Code
os.makedirs("figures", exist_ok=True)daily_series = df_INMET['RADIACAO_GLOBAL_IMPUTADA'].resample('D').mean().dropna()df_daily = daily_series.to_frame(name='RADIACAO_GLOBAL_IMPUTADA')df_daily['Day'] = df_daily.index.day_name()days_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']fig = px.box( df_daily, x='Day', y='RADIACAO_GLOBAL_IMPUTADA', category_orders={'Day': days_order}, labels={'Day': 'Day of the Week','RADIACAO_GLOBAL_IMPUTADA': 'Global Radiation (Imputed)' }, title='Distribution of Imputed Global Radiation by Day of the Week')fig.update_layout( title_font_size=18, xaxis_title_font_size=14, yaxis_title_font_size=14)fig.write_html("figures/Boxplot_GlobalRadiation_ByDay_Plotly.html")fig.show()
Code
os.makedirs("figures", exist_ok=True)daily_series = df_INMET['RADIACAO_GLOBAL_IMPUTADA'].resample('D').mean().dropna()df_daily = daily_series.to_frame(name='RADIACAO_GLOBAL_IMPUTADA')df_daily['Day'] = df_daily.index.day_name()plt.figure(figsize=(10, 6))sns.boxplot( data=df_daily, x='Day', y='RADIACAO_GLOBAL_IMPUTADA', order=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'])plt.title('Distribution of Imputed Global Radiation by Day of the Week', fontsize=18)plt.xlabel('Day of the Week', fontsize=14)plt.ylabel('Global Radiation (Imputed)', fontsize=14)plt.grid(True)plt.tight_layout()plt.savefig("figures/Boxplot_GlobalRadiation_ByDay.png", dpi=300)plt.show()
Code
plt.figure(figsize=(6, 4))autocorrelation_plot(daily_series.tail(200))plt.title("Autocorrelation - Global Radiation", fontsize=12)plt.grid(True)plt.tight_layout()plt.savefig("figures/Autocorrelation_GlobalRadiation_Daily_200days.png", dpi=300)plt.show()
df_mensual = df_INMET['RADIACAO_GLOBAL_IMPUTADA'].resample('M').mean().to_frame(name='Promedio_Radiacion_Mensual')variables = ['Promedio_Radiacion_Mensual']resumen = {}for var in variables: data = df_mensual[var] resumen[var] = {'N_records': len(data),'μ': data.mean(),'σ': data.std(),'y_min': data.min(),'Q1': data.quantile(0.25),'x̄': data.median(),'Q3': data.quantile(0.75),'y_max': data.max(),'Kurtosis': data.kurtosis(),'Skewness': data.skew() }df_resumen = pd.DataFrame(resumen)df_resumen
Promedio_Radiacion_Mensual
N_records
74.000000
μ
284.331526
σ
49.663561
y_min
225.783565
Q1
249.974238
x̄
274.986044
Q3
313.849497
y_max
411.725806
Kurtosis
1.182565
Skewness
1.264723
Code
plt.figure(figsize=(14, 5))plt.plot(df_mensual, color='blue')plt.title('Monthly Time Series - Global Radiation', fontsize=18)plt.xlabel('Date', fontsize=18)plt.ylabel('Radiation (W/m²)', fontsize=18)plt.xticks(fontsize=16) plt.yticks(fontsize=16)plt.grid(True)plt.tight_layout()plt.savefig("figures/Monthly_GlobalRadiation_TimeSeries.png", dpi=300)plt.show()
Code
descomposicion = seasonal_decompose(df_mensual, model='additive', period=12)descomposicion.plot()plt.suptitle('Decomposition of Global Radiation (Monthly Cycle)', fontsize=16)plt.tight_layout()plt.show()